package com.piece.core.framework.util.security.coder;

import com.piece.core.framework.util.string.StringUtil;
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.stream.Collectors;

/**
 * B编码解码器
 *
 * <table border="1">
 * <caption>类型</caption>
 * <tr>
 * <th>符号</th>
 * <th>类型</th>
 * </tr>
 * <tr>
 * <td align="center">{@code i}</td>
 * <td>数字：{@code Long}</td>
 * </tr>
 * <tr>
 * <td align="center">{@code l}</td>
 * <td>列表：{@code List}</td>
 * </tr>
 * <tr>
 * <td align="center">{@code d}</td>
 * <td>字典：{@code Map}</td>
 * </tr>
 * <tr>
 * <td align="center">{@code e}</td>
 * <td>结尾</td>
 * </tr>
 * </table>
 * <p>所有类型除了{@code Long}，其他均为{@code byte[]}，需要自己进行类型转换。</p>
 * <p>解析前必须调用{@link #nextType()}、{@link #nextMap()}、{@link #nextList()}任一方法</p>
 */
@Slf4j
public class BEncodeDecoder {

    /**
     * B编码数据类型
     */
    public enum Type {
        /**
         * Map
         */
        MAP,
        /**
         * List
         */
        LIST,
        /**
         * 未知
         */
        NONE;
    }

    /**
     * 结尾
     */
    public static final char TYPE_E = 'e';
    /**
     * 数字
     */
    public static final char TYPE_I = 'i';
    /**
     * List
     */
    public static final char TYPE_L = 'l';
    /**
     * Map
     */
    public static final char TYPE_D = 'd';
    /**
     * 分隔符
     */
    public static final char SEPARATOR = ':';
    /**
     * 数据类型
     */
    private Type type;
    /**
     * List
     */
    private List<Object> list;
    /**
     * Map
     */
    private Map<String, Object> map;
    /**
     * 原始数据
     */
    private final ByteArrayInputStream inputStream;

    private BEncodeDecoder(byte[] bytes) {
        if (null == bytes) {
            throw new RuntimeException("B编码内容错误: 数据为空");
        }
        if (bytes.length < 2) {
            throw new RuntimeException("B编码内容错误: 长度小于两位");
        }
        this.inputStream = new ByteArrayInputStream(bytes);
    }

    /**
     * 创建B编码解码器
     */
    public static final BEncodeDecoder newInstance(String content) {
        if (content == null) {
            throw new RuntimeException("B编码内容错误: 数据为空");
        }
        return new BEncodeDecoder(content.getBytes());
    }

    /**
     * 创建B编码解码器
     */
    public static final BEncodeDecoder newInstance(ByteBuffer buffer) {
        if (null == buffer) {
            throw new RuntimeException("B编码内容错误: 数据为空");
        }
        final byte[] bytes = new byte[buffer.remaining()];
        buffer.get(bytes);
        return new BEncodeDecoder(bytes);
    }

    /**
     * 创建B编码解码器
     */
    public static final BEncodeDecoder newInstance(byte[] bytes) {
        return new BEncodeDecoder(bytes);
    }

    /**
     * 判断是否含有更多数据
     */
    public boolean more() {
        return null != this.inputStream && this.inputStream.available() > 0;
    }

    /**
     * 数据是否为空
     */
    public boolean isEmpty() {
        if (Type.LIST == this.type) {
            return null == this.list;
        } else if (Type.MAP == this.type) {
            return this.map == null;
        } else {
            return true;
        }
    }

    /**
     * 数据是否非空
     */
    public boolean isNotEmpty() {
        return !isEmpty();
    }

    /**
     * 获取下一个数据类型
     * <p>获取下一个数据类型，同时解析下一个数据。</p>
     */
    public Type nextType() throws RuntimeException {
        if (!more()) {
            return this.type = Type.NONE;
        }
        final char type = (char) this.inputStream.read();
        switch (type) {
            case TYPE_D:
                this.map = readMap(this.inputStream);
                return this.type = Type.MAP;
            case TYPE_L:
                this.list = readList(this.inputStream);
                return this.type = Type.LIST;
            default:
                return this.type = Type.NONE;
        }
    }

    /**
     * 获取下一个List
     */
    public List<Object> nextList() throws RuntimeException {
        final Type type = nextType();
        if (Type.LIST == type) {
            return this.list;
        }
        return Collections.emptyList();
    }

    /**
     * 获取下一个Map
     */
    public Map<String, Object> nextMap() throws RuntimeException {
        final Type type = nextType();
        if (Type.MAP == type) {
            return this.map;
        }
        return new HashMap<>();
    }

    /**
     * 读取剩余所有数据
     */
    public byte[] oddBytes() throws IOException {
        if (null == this.inputStream) {
            return null;
        }
        return toByte(this.inputStream);
    }

    private byte[] toByte(InputStream is) throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        byte[] bytes = new byte[0];
        try {
            byte[] buff = new byte[1024];
            int rc = 0;
            while ((rc = is.read(buff, 0, 1024)) > 0) {
                output.write(buff, 0, rc);
            }
            bytes = output.toByteArray();
        } catch (IOException e) {
            log.error("读取文件流到byte数组异常：{}", e.getMessage());
        } finally {
            output.close();
        }
        return bytes;
    }

    /**
     * 读取剩余所有数据并转为字符串
     */
    public String oddString() throws IOException {
        final byte[] bytes = oddBytes();
        if (null == bytes) {
            return null;
        }
        return new String(bytes);
    }

    /**
     * 读取数值
     */
    private Long readLong(ByteArrayInputStream inputStream) {
        int index;
        char indexChar;
        final StringBuilder valueBuilder = new StringBuilder();
        while ((index = inputStream.read()) != -1) {
            indexChar = (char) index;
            if (indexChar == TYPE_E) {
                final String number = valueBuilder.toString();
                if (!StringUtil.isNumeric(number)) {
                    throw new RuntimeException("B编码格式错误：[" + number + "] 非数字");
                }
                return Long.valueOf(number);
            } else {
                valueBuilder.append(indexChar);
            }
        }
        return 0L;
    }

    /**
     * 读取Map
     */
    private Map<String, Object> readMap(ByteArrayInputStream inputStream) throws RuntimeException {
        int index;
        char indexChar;
        String key = null;
        final Map<String, Object> map = new LinkedHashMap<>();
        final StringBuilder lengthBuilder = new StringBuilder();
        while ((index = inputStream.read()) != -1) {
            indexChar = (char) index;
            switch (indexChar) {
                case TYPE_E:
                    return map;
                case TYPE_I:
                    if (key != null) {
                        map.put(key, readLong(inputStream));
                        key = null;
                    }
                    break;
                case TYPE_L:
                    if (key != null) {
                        map.put(key, readList(inputStream));
                        key = null;
                    }
                    break;
                case TYPE_D:
                    if (key != null) {
                        map.put(key, readMap(inputStream));
                        key = null;
                    }
                    break;
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    lengthBuilder.append(indexChar);
                    break;
                case SEPARATOR:
                    if (lengthBuilder.length() > 0) {
                        final byte[] bytes = read(lengthBuilder, inputStream);
                        if (null == key) {
                            key = new String(bytes);
                        } else {
                            map.put(key, bytes);
                            key = null;
                        }
                    }
                    break;
                default:
                    break;
            }
        }
        return map;
    }

    /**
     * 读取List
     */
    private List<Object> readList(ByteArrayInputStream inputStream) throws RuntimeException {
        int index;
        char indexChar;
        final List<Object> list = new ArrayList<>();
        final StringBuilder lengthBuilder = new StringBuilder();
        while ((index = inputStream.read()) != -1) {
            indexChar = (char) index;
            switch (indexChar) {
                case TYPE_E:
                    return list;
                case TYPE_I:
                    list.add(readLong(inputStream));
                    break;
                case TYPE_L:
                    list.add(readList(inputStream));
                    break;
                case TYPE_D:
                    list.add(readMap(inputStream));
                    break;
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    lengthBuilder.append(indexChar);
                    break;
                case SEPARATOR:
                    if (lengthBuilder.length() > 0) {
                        final byte[] bytes = read(lengthBuilder, inputStream);
                        list.add(bytes);
                    }
                    break;
                default:
                    break;
            }
        }
        return list;
    }

    /**
     * 读取符合长度的字节数组
     */
    private byte[] read(StringBuilder lengthBuilder, ByteArrayInputStream inputStream) throws RuntimeException {
        final String number = lengthBuilder.toString();
        if (!StringUtil.isNumeric(number)) {
            throw new RuntimeException("B编码格式错误 [" + number + "] 非数字");
        }
        final int length = Integer.parseInt(number);
        if (length < 0 || length > 1024) {
            throw new RuntimeException("网络包大小异常: 最大支持：1024, 当前：" + length);
        }
        lengthBuilder.setLength(0);
        final byte[] bytes = new byte[length];
        try {
            final int readLength = inputStream.read(bytes);
            if (readLength != length) {
                log.warn("B编码错误（读取长度和实际长度不符）：{}-{}", length, readLength);
            }
        } catch (IOException e) {
            log.error("B编码读取异常", e);
        }
        return bytes;
    }

    /**
     * 获取对象
     */
    public Object get(String key) {
        return get(this.map, key);
    }

    /**
     * 获取对象
     */
    public static final Object get(Map<?, ?> map, String key) {
        if (null == map) {
            return null;
        }
        return map.get(key);
    }

    /**
     * 获取字节
     */
    public Byte getByte(String key) {
        return getByte(this.map, key);
    }

    /**
     * 获取字节
     */
    public static final Byte getByte(Map<?, ?> map, String key) {
        final Long value = getLong(map, key);
        if (null == value) {
            return null;
        }
        return value.byteValue();
    }

    /**
     * 获取数值
     */
    public Integer getInteger(String key) {
        return getInteger(this.map, key);
    }

    /**
     * 获取数值
     */
    public static final Integer getInteger(Map<?, ?> map, String key) {
        final Long value = getLong(map, key);
        if (null == value) {
            return null;
        }
        return value.intValue();
    }

    /**
     * 获取数值
     */
    public Long getLong(String key) {
        return getLong(this.map, key);
    }

    /**
     * 获取数值
     */
    public static final Long getLong(Map<?, ?> map, String key) {
        if (null == map) {
            return null;
        }
        return (Long) map.get(key);
    }

    /**
     * 获取字符串
     */
    public String getString(String key) {
        return getString(this.map, key);
    }

    /**
     * 获取字符串
     */
    public String getString(String key, String encoding) {
        return getString(this.map, key, encoding);
    }

    /**
     * 获取字符串
     */
    public static final String getString(Map<?, ?> map, String key) {
        return getString(map, key, null);
    }

    /**
     * 获取字符串
     */
    public static final String getString(Map<?, ?> map, String key, String encoding) {
        final byte[] bytes = getBytes(map, key);
        if (null == bytes) {
            return null;
        }
        return StringUtil.getStringCharset(bytes, encoding);
    }

    /**
     * 获取字符数组
     */
    public byte[] getBytes(String key) {
        return getBytes(this.map, key);
    }

    /**
     * 获取字符数组
     */
    public static final byte[] getBytes(Map<?, ?> map, String key) {
        if (null == map) {
            return null;
        }
        return (byte[]) map.get(key);
    }

    /**
     * 获取集合
     */
    public List<Object> getList(String key) {
        return getList(this.map, key);
    }

    /**
     * 获取集合
     */
    public static final List<Object> getList(Map<?, ?> map, String key) {
        if (null == map) {
            return Collections.emptyList();
        }
        final List result = (List<?>) map.get(key);
        if (null == result) {
            return Collections.emptyList();
        }
        return (List<Object>) result.stream().collect(Collectors.toList());
    }

    /**
     * 获取Map
     */
    public Map<String, Object> getMap(String key) {
        return getMap(this.map, key);
    }

    /**
     * 获取Map
     */
    public static final Map<String, Object> getMap(Map<?, ?> map, String key) {
        if (null == map) {
            return new HashMap<>();
        }
        final Map<String, Object> result = (Map<String, Object>) map.get(key);
        if (null == result) {
            return new HashMap<>();
        }

        return result.entrySet().stream()
                .filter((entry) -> null != entry.getKey())
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b, LinkedHashMap::new));
    }
}
